﻿
using System;
using System.Collections.Generic;
using System.Linq;

namespace EmperialApps.WeatherSpark.Data {

    /// <summary>Manages searches for locations.</summary>
    public sealed partial class LocationManager {

        /// <summary>Gets the search terms contained in the specified search string.</summary>
        public static string[] GetSearchTerms( string search ) {
            if( _lastSearchTerms.Item1 != search )
                _lastSearchTerms = Pair.Create( search, Split( search ) );

            return _lastSearchTerms.Item2;
        }

        /// <summary>Tests whether the specified item matches all of the search terms.</summary>
        public static bool IsMatch( string itemString, IEnumerable<string> searchTerms ) {
            return searchTerms.All( term => itemString.IndexOf( term, StringComparison.CurrentCultureIgnoreCase ) >= 0 );
        }


        /// <summary>Returns all locations saved for the given search text, or begins a new download.</summary>
        public bool TryGetLocations( string search, out Place[] locations ) {
            // Check for existing stored locations.
            Place[] directPlaces = this.Store.Load( search, null );

            // Add all saved locations that match current search text.
            locations = this.GetAllMatchingPlaces( search, directPlaces );

            // Return true if store contained locations.
            return directPlaces != null;
        }

        /// <summary>Starts location download for the specified search text.</summary>
        public void DownloadLocations( string search ) {
            this.Download( search, null );
        }


        #region Private Members

        private static readonly Place[] EmptyPlaces = new Place[0];
        private static Pair<string, string[]> _lastSearchTerms;

        private readonly Dictionary<string, string> _pending = new Dictionary<string, string>( );
        private string _lastSearch;


        partial void InitializeInstance( ) {
            this.Store.DownloadCompleted += this.OnDownloadCompleted;
        }


        private static string[] Split( string search ) {
            // Split based on the separate words contained in the search text.
            var items = new List<string>( );
            for( int i = 0, last = 0; i <= search.Length; ++i ) {
                if( i < search.Length && char.IsLetter( search, i ) )
                    continue;

                int length = i - last;
                if( length > 0 )
                    items.Add( search.Substring( last, length ) );
                last = i + 1;
            }

            return items.ToArray( );
        }


        private Place[] GetPlaces( string search ) {
            Place[] places = this.Store.Load( search, null );
            return places ?? EmptyPlaces;
        }

        private Place[] GetAllMatchingPlaces( string search, Place[] places ) {
            if( search.Length < 2 )
                return EmptyPlaces;

            string[] searchTerms = GetSearchTerms( search );
            var previousPlaces =
                Enumerable.Range( 2, search.Length - 2 )
                    .SelectMany( length => GetPlaces( search.Substring( 0, length ) ) )
                    .Where( place => IsMatch( place.ToString( ), searchTerms ) );

            Place[] matchingPlaces =
                (places ?? EmptyPlaces).Concat( previousPlaces )
                    .Distinct( )
                    .ToArray( );
            return matchingPlaces;
        }


        private void Download( string search, string originalSearch ) {
            if( this._pending.ContainsKey( search ) || this.Store.Load( search, null ) != null )
                return;

            if( originalSearch == null )
                this._lastSearch = search;

            this._pending.Add( search, originalSearch );
            this.Store.BeginDownload( search );
        }

        private void OnDownloadCompleted( object sender, LocationEventArgs e ) {
            // Save result.
            this.Store.Save( e );

            // Remove search from pending list.
            string originalSearch;
            string search = e.Search;
            if( this._pending.TryGetValue( search, out originalSearch ) )
                this._pending.Remove( search );

            // If search did not produce any results, and search has alternate form, begin new download.
            if( e.Error == null && e.Places.Length == 0 && originalSearch == null && search.Contains( " " ) ) {
                int spaceIndex = search.LastIndexOf( ' ' );
                string alternateSearch = search.Substring( 0, spaceIndex ) + "," + search.Substring( spaceIndex );
                this.Download( alternateSearch, search );
            }
            else {
                // If search was an alternate, save results to original.
                if( originalSearch != null ) {
                    this.Store.Save( new LocationEventArgs( originalSearch, e.Places, e.Error ) );
                    search = originalSearch;
                }

                // If this was the last search made, raise progress event.
                if( this._lastSearch == search ) {
                    int count = (e.Places ?? EmptyPlaces).Length;

                    string error =
                        e.Error != null
                            ? e.Error.GetType( ).Name.Replace( "Exception", " error" )
                            : null;
                    this.OnProgress( count, Pair.Create( search, error ) );
                }
            }
        }

        #endregion
    }

}
